package logic;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.security.InvalidKeyException;
import java.util.LinkedList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.Vector;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;

import serializer.SimpleXMLSerializer;

import comm.NetworkInterfaceHandler;
import comm.UdpBroadcaster;
import comm.UdpReceiver;

import constants.EUdpGroupName;
import constants.EUdpPortNum;
//import encryption.Encryption;


public class UdpCommPackage extends Observable implements Observer{
	private UdpBroadcaster broadcaster;
	private UdpReceiver receiver;
	private Vector<InetAddress> addressList;
	private boolean hasMulticastNi;
	//private Encryption encryption;
	private SimpleXMLSerializer<SerialString> serialStringSerializer;

	public UdpCommPackage () throws IOException{
		/*this sets up Receivers and Broadcasters in an order that is
		the opposite of the expected flow to ensure that a proper response will occur

		The pingBroadcaster needs a pingReceiver to listen for pings
		 */

		//set up serializer
		serialStringSerializer = new SimpleXMLSerializer<SerialString>();
		
		//set properties
		System.setProperty("java.net.preferIPv4Stack", "true");
		hasMulticastNi = false;
		
		List<NetworkInterface> nics = new NetworkInterfaceHandler().getNics();
		if(nics.size() > 0){
			hasMulticastNi = true;

			//gets the first network interface, though any should work
			NetworkInterface ni = nics.get(0);
			
			//sets up the Ping receiver
			new Thread(
					receiver = new UdpReceiver(
							ni,
							EUdpPortNum.PING.getPortNum(),
							EUdpGroupName.PING_GROUP.getGroupName())
			).start();
			receiver.addObserver(this);

			//sets up the Ping broadcaster
			new Thread (
					broadcaster = new UdpBroadcaster(
							ni,
							EUdpPortNum.PING.getPortNum(),
							EUdpGroupName.PING_GROUP.getGroupName())
			).start();

		}
		
		/*
		String test = "test";
		encryption = Encryption.getInstance();
		
		try {
			System.out.println(new String(
					encryption.decrypt(
							encryption.encrypt(test.getBytes())
					)
				)
			);
		} catch (InvalidKeyException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (BadPaddingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		*/
		//creating the address list to record addresses
		addressList = new Vector<InetAddress>();
	}

	@Override
	public void update(Observable receiver, Object datagramPacket) {
		//handles updates from the receivers

		DatagramPacket packet =((DatagramPacket)datagramPacket);  

		InetAddress address = packet.getAddress();//gets the sender address
		addAddress(address);//add to address list, if unique

		//get the data from the packet
		byte[] bytes = packet.getData();
		
		try {
			//tries to cast the data as a SerialString, which is a serializable wrapper around String 
			SerialString serialString = serialStringSerializer.deserialize(SerialString.class, bytes);

			if(serialString.getString().equals("ping")){
				handlePing(address);
			}
			else if(serialString.getString().equals("response")){
				handleResponse(address);
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 

	}

	public void sendPing() throws Exception{
		//sends a ping request out to listening receivers
		if(hasMulticastNi){
			SerialString string = new SerialString("ping");
			//byte[] bytes = Serializer.getInstance().serialize(string);
			byte[] bytes = serialStringSerializer.serialize(string);
			
			//broadcasts information to the ping receiver
			broadcaster.sendPacket(bytes, EUdpPortNum.PING.getPortNum());
		}
	}
	private void handlePing(InetAddress address) throws Exception{
		//handles ping responses
		if (hasMulticastNi){
			try {

				//notify observers with text message
				setChanged();
				notifyObservers(address.getHostAddress()+": Marco!\nYou say: Polo!");

				//respond to ping
				SerialString string = new SerialString("response");
				
				//byte[] bytes = Serializer.getInstance().serialize(string);
				byte[] bytes = serialStringSerializer.serialize(string);
				
				broadcaster.sendPacket(bytes, EUdpPortNum.PING.getPortNum());
			} catch (UnknownHostException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	private void handleResponse(InetAddress address){

		//notify observers with text message
		setChanged();
		notifyObservers(address.getHostAddress()+": Polo!");

	}

	private void addAddress(InetAddress address){
		// adds an address to the address list if not already present
		boolean found = false;
		int i = 0;
		while(i<addressList.size()){
			//if equivalent address is found, ignore the address
			if (addressList.get(i).getHostAddress().equals(address.getHostAddress())){
				return;
			}
			i++;
		}
		if (found == false){
			addressList.add(address);	
		}
	}

	public String[] getAddressList(){
		//returns the address list as an array of strings
		LinkedList<String> linkedAddresses = new LinkedList<String>();
		for (InetAddress address :addressList){
			linkedAddresses.add(address.getHostAddress());
		}
		return linkedAddresses.toArray(new String[0]);
	}
}
